using System;
using System.Data;
using System.Net;
using System.Text;
using BSDS.Core.Data.Local;
using BSDS.Core.Data.Remote.Upload;
using BSDS.Core.Infrastructure.Configuration;
using BSDS.Core.Infrastructure.Logging;
using BSDS.Core.Infrastructure.RemoteCommunication;
using BSDS.Core.Model.Data;
using BSDS.Core.Model.Services.Jobs;
using BSDS.Core.Model.Services.Jobs.Core;
using BSDS.Core.Model.Services.Uploading.DataProviders;
using BSDS.Core.Model.Validation;

namespace BSDS.Core.Model.Services.Uploading
{
    public class UploadService : IJobStateProvider
    {
        private const string GetStatusQueryTemplate =
            "http://spatial.virtualearth.net/REST/v1/Dataflows/LoadDataSource/{0}?output=xml&key={1}";

        private const string UploadDataQueryTemplate =
            "http://spatial.virtualearth.net/REST/v1/Dataflows/LoadDataSource?";

        private readonly DataProviderFactory dataProviderFactory;
        private readonly DataRepository dataRepository;
        private readonly JobRepository jobRepository;
        private readonly Logger logger;
        private readonly ServiceClient serviceClient;
        private readonly UploadResponseParser parser;
        private readonly IBingKey bingKey;

        internal UploadService(DataProviderFactory dataProviderFactory, DataRepository dataRepository, JobRepository jobRepository,
            ServiceClient serviceClient, UploadResponseParser parser, Logger logger, IBingKey bingKey)
        {
            this.dataProviderFactory = dataProviderFactory;
            this.dataRepository = dataRepository;
            this.jobRepository = jobRepository;
            this.logger = logger;
            this.serviceClient = serviceClient;
            this.parser = parser;
            this.bingKey = bingKey;
        }

        public UploadJob ScheduleUpload(BsdsData data, string dataSourceName, DataFormat dataFormat, string entityTypeName, string queryKey)
        {
            logger.Log("Starting upload for data source: " + dataSourceName);

            var validationResult = data.Validate();

            if (validationResult.Status != ValidationStatus.Success)
            {
                throw new DataException("Upload data validation error: " + validationResult.Description);
            }

            var uploadFilename = dataRepository.SaveForUpload(data.Entities, dataFormat, entityTypeName, "upload");

            using (var dataProvider = dataProviderFactory.Create(uploadFilename, entityTypeName))
            {
                var job = ScheduleUpload(dataProvider, dataSourceName, queryKey);
                job.RecordsNumber = data.Entities.Length;
                job.DataSourceName = dataSourceName;

                jobRepository.Save(job);

                return job;
            }
        }

        private UploadJob ScheduleUpload(IDataProvider dataProvider, string dataSourceName, string queryKey)
        {
            var queryStringBuilder = new StringBuilder();

            queryStringBuilder.Append("dataSourceName=").Append(Uri.EscapeUriString(dataSourceName));
            queryStringBuilder.Append("&");
            queryStringBuilder.Append("loadOperation=").Append(Uri.EscapeUriString("complete"));
            queryStringBuilder.Append("&");
            queryStringBuilder.Append("input=").Append(Uri.EscapeUriString(dataProvider.Format.Value));
            queryStringBuilder.Append("&");
            queryStringBuilder.Append("output=").Append(Uri.EscapeUriString("xml"));
            queryStringBuilder.Append("&");
            queryStringBuilder.Append("key=").Append(Uri.EscapeUriString(bingKey.Value));
            if (!string.IsNullOrEmpty(queryKey))
            {
                queryStringBuilder.Append("&queryKey=").Append(queryKey);
            }

            var uri = UploadDataQueryTemplate + queryStringBuilder;

            var result = serviceClient.SendRequest(uri, dataProvider);

            if (result.Status != HttpStatusCode.Created)
            {
                logger.Log("Error uploading data.", result);

                throw new Exception("Cannot upload data.");
            }

            var job = parser.ParseCreateUploadJobResponse(result.Content);

            return job;
        }
       
        public JobState QueryState(string jobId)
        {
            var uri = string.Format(GetStatusQueryTemplate, jobId, bingKey);

            var response = serviceClient.SendRequest(uri, "GET");
            
            return parser.ParseGetStatusResponse(jobId, response.Content);
        }
    }
}